// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default

#include "qwindowswindowclassregistry.h"

#include <QtCore/qlibraryinfo.h>
#include <QtCore/quuid.h>

#include "qwindowscontext.h"
#include "qwindowswindowclassdescription.h"

QT_BEGIN_NAMESPACE

using namespace Qt::StringLiterals;

Q_LOGGING_CATEGORY(lcQpaWindowClass, "qt.qpa.windowclass")

QWindowsWindowClassRegistry *QWindowsWindowClassRegistry::m_instance = nullptr;

QWindowsWindowClassRegistry::QWindowsWindowClassRegistry(WNDPROC defaultProcedure)
    : m_defaultProcedure(defaultProcedure)
{
    m_instance = this;
}

QWindowsWindowClassRegistry::~QWindowsWindowClassRegistry()
{
    unregisterWindowClasses();

    m_instance = nullptr;
}

QWindowsWindowClassRegistry *QWindowsWindowClassRegistry::instance()
{
    return m_instance;
}

QString QWindowsWindowClassRegistry::classNamePrefix()
{
    static QString result;
    if (result.isEmpty()) {
        QTextStream str(&result);
        str << "Qt" << QT_VERSION_MAJOR << QT_VERSION_MINOR << QT_VERSION_PATCH;
        if (QLibraryInfo::isDebugBuild())
            str << 'd';
#ifdef QT_NAMESPACE
#  define xstr(s) str(s)
#  define str(s) #s
        str << xstr(QT_NAMESPACE);
#  undef str
#  undef xstr
#endif
    }
    return result;
}

QString QWindowsWindowClassRegistry::registerWindowClass(const QWindowsWindowClassDescription &description)
{
    QString className = description.name;

    if (description.shouldAddPrefix)
        className = classNamePrefix() + className;

    // since multiple Qt versions can be used in one process
    // each one has to have window class names with a unique name
    // The first instance gets the unmodified name; if the class
    // has already been registered by another instance of Qt then
    // add a UUID. The check needs to be performed for each name
    // in case new message windows are added (QTBUG-81347).
    // Note: GetClassInfo() returns != 0 when a class exists.
    if (shouldDecorateWindowClassName(description))
        className += QUuid::createUuid().toString();

    if (m_registeredWindowClassNames.contains(className))        // already registered in our list
        return className;

    const auto appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));

    WNDCLASSEX wc{};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = description.style;
    wc.lpfnWndProc = description.procedure;
    wc.hInstance = appInstance;
    wc.hbrBackground = description.brush;
    if (description.hasIcon) {
        wc.hIcon = static_cast<HICON>(LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
        if (wc.hIcon) {
            int sw = GetSystemMetrics(SM_CXSMICON);
            int sh = GetSystemMetrics(SM_CYSMICON);
            wc.hIconSm = static_cast<HICON>(LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, sw, sh, 0));
        }
        else {
            wc.hIcon = static_cast<HICON>(LoadImage(nullptr, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED));
        }
    }

    wc.lpszClassName = reinterpret_cast<LPCWSTR>(className.utf16());
    ATOM atom = RegisterClassEx(&wc);
    if (!atom)
        qCWarning(lcQpaWindowClass) << "Failed to register window class" << className
            << "(" << qt_error_string(-1) << ")";

    m_registeredWindowClassNames.insert(className);

    qCDebug(lcQpaWindowClass).nospace() << __FUNCTION__ << ' ' << className << ' ' << description << " atom=" << atom;

    return className;
}

QString QWindowsWindowClassRegistry::registerWindowClass(const QWindow *window)
{
    return registerWindowClass(QWindowsWindowClassDescription::fromWindow(window, m_defaultProcedure));
}

QString QWindowsWindowClassRegistry::registerWindowClass(QString name, WNDPROC procedure)
{
    return registerWindowClass(QWindowsWindowClassDescription::fromName(name, procedure));
}

bool QWindowsWindowClassRegistry::shouldDecorateWindowClassName(const QWindowsWindowClassDescription &description) const
{
    return shouldDecorateWindowClassName(description.name, description.procedure);
}

bool QWindowsWindowClassRegistry::shouldDecorateWindowClassName(const QString &name, WNDPROC procedure) const
{
    const auto appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));

    WNDCLASS wc{};

    return GetClassInfo(appInstance, reinterpret_cast<LPCWSTR>(name.utf16()), &wc) != FALSE
        && wc.lpfnWndProc != procedure;
}

void QWindowsWindowClassRegistry::unregisterWindowClasses()
{
    const auto appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));

    for (const QString &name : std::as_const(m_registeredWindowClassNames)) {
        if (!UnregisterClass(reinterpret_cast<LPCWSTR>(name.utf16()), appInstance) && QWindowsContext::verbose)
            qCWarning(lcQpaWindowClass) << "Failed to unregister window class" << name
                << "(" << qt_error_string(-1) << ")";
    }
    m_registeredWindowClassNames.clear();
}

QT_END_NAMESPACE
